#!/home/joshua/.pyenv/versions/anaconda3/bin/python
"""
要求：

    1. 程序名称 contact
    2. 程序功能：
        1. 添加联系人到数据库中
        2. 查询数据库中的联系人
        3. 删除联系人
        4. 修改联系人的属性
    3. 联系人的信息有这些：名字，性别，电话
    4. 使用面向对象的编码方式
    5. 程序的运行方式
        在命令行输入 contact，然后回车可以执行
"""

import sys
from os.path import basename


class Contact:

    def __init__(self, name=None, gender=None, phone=None):
        #
        # 这里的三个参数全部都设定了默认值为None，
        # 意味着在实例化的时候，可以只提供部分参数，或者不提供任何参数
        #
        self.name = name
        self.gender = gender
        self.phone = phone

    #
    # 这种 @xx 的语法是装饰器语法，classmethod这个名字是python内置模块里的名字
    # 内置模块顶层的名字在任何时候都是可用的，如果需要操作内置模块，可以将该
    # 模块导入，导入方法：import builtins
    #
    # 装饰器可以对被装饰的对象做修改，可以改其属性，也可以改其行为。
    # 装饰器通常是一个函数，也可以是自定义类的对象，装饰器会返回一个与被装饰对象
    # 类似对象，此对象可以是被装饰的对象本身，也可以不是。
    #
    @classmethod
    def open_db(cls):
        # getattr是python的内置函数，存在于builtins模块中。
        # 为什么这里不使用  _db = cls._db 这样的语句呢？
        # 当然可以使用，这在 _db 属性尚不存在时会有异常发生
        _db = getattr(cls, '_db', None)

        #
        # 这里的if判断确保了只初始化数据库一次。
        #
        if not _db:     # 当为None时，也就是说尚未设置过此属性时
            cls._db = Database()
        return cls._db

    def fill(self):
        #
        # 填充联系人信息
        #

        # 当修改一个记录时，在覆盖旧名字之前，先保存旧名字，以备后续使用
        # 为什么需要保存原来的名字？
        # 因为此函数不仅仅用于新建联系人时使用，它也用于修改联系人，
        # 当修改联系人时，会先删除旧记录，再添加修改后的记录，删除旧记录时
        # 依靠名字来定位，因此需要保留旧的名字，才能定位旧的记录
        #
        if getattr(self, 'name', None):
            self.orig_name = self.name
        else:
            self.orig_name = None

        for key in ['name', 'gender', 'phone']:
            #
            # 获取原来的值，为什么？
            # 因为，如果存在一个旧值，那么就应该在提示字符中显示出来，
            # 告知用户，让用户知道，如果直接按Enter键，将会使用的是什么值。
            #
            value = getattr(self, key, None)

            # 构建提示符
            ps = '%s: ' % key.title()
            if value:
                ps += '[%s] ' % value

            #
            # 这里的or的用法是这样：
            # 在表达式 aa or bb 中，如果aa相当于True，表达式就返回aa，否则就返回bb
            #
            # 因此下面的语句就相当于
            # new_value = input(ps)
            # if new_value:
            #     value = new_value
            value = input(ps) or value
            setattr(self, key, value)

        # 假设不使用for循环，那么这个函数的逻辑可以改成这样，这样就有许多重复的代码：

        # # 获取名字
        # ps = 'name: '
        # orig_value = getattr(self, 'name', None)
        # if orig_value:
        #     ps += '[%s] ' % orig_value
        # name = input(ps)

        # # 获取年龄
        # ps = 'age: '
        # orig_value = getattr(self, 'age', None)
        # if orig_value:
        #     ps += '[%s] ' % orig_value
        # age = input(ps)

        # # 获取电话
        # ps = 'phone: '
        # orig_value = getattr(self, 'phone', None)
        # if orig_value:
        #     ps += '[%s] ' % orig_value
        # age = input(ps)

    def save(self):
        #
        # 因为我们把操作数据库的代码放到数据库类中，所以这里不需要
        # 关心如何写数据，仅需按照数据库的接口(API)来调用即可，
        # 这里所使用的接口就是数据库类的save方法。
        #
        db = self.open_db()
        if not db.record_exists(self.name):
            db.save({'name': self.name,
                     'gender': self.gender,
                     'phone': self.phone}, orig_name=self.orig_name)
        else:
            print('name conflict')

    def delete(self):
        db = self.open_db()
        db.delete(self.name)

    @classmethod
    def search(cls, name):
        db = cls.open_db()
        # 如果找不到，db.search返回None.
        r = db.search(name)
        #
        # 以下两种写法，推荐用第一种，因为表达起来更想自然的英文
        #   if r is not None:
        #   if not r is None:
        # 类似的表达式还有这个：
        #   a is not b
        #   not a is b
        #
        if r is not None:
            contact = cls(name=r['name'], gender=r['gender'], phone=r['phone'])
            return contact

    @classmethod
    def list_all(cls):
        db = cls.open_db()
        for r in db.records:
            #
            # 这里把构建格式化字符串和格式化这两步分开，是为了使语句长度不超过
            # 79这个建议值，目的是为了是代码更好看。
            #
            # 如果有需要，是可以突破这个限制的，前提就是应当在同一个程序中保持相同
            # 的行长度，就是说要统一了。
            #
            # 还有另外的情形也要考虑到统一的问题，比如说缩进字符的选择。
            # python接受用空格或者Tab字符来做缩进，但是Python3不允许混合使用两者。
            #
            fmt = 'Name: %s, Gender: %s, Phone: %s'
            text = fmt % (r['name'], r['gender'], r['phone'])
            print(text)

    def __str__(self):
        fmt = 'Name: %s, Gender: %s, Phone: %s'
        text = fmt % (self.name, self.gender, self.phone)
        return text


class Database:

    dbpath = 'contact.db'
    sep = ':'

    def __init__(self):
        #
        # 这个函数所做的，大体上就是读入数据库中所有的记录，
        # 存放到数据库实例对象中。这种读入所有记录并存放到
        # 内存中的做法，仅在数据量非常小的是才才能使用。
        #
        file = self.open()
        lines = file.read().splitlines()
        records = []
        for line in lines:
            name, gender, phone = line.split(Database.sep)
            record = {'name': name, 'gender': gender, 'phone': phone}
            records.append(record)
        self.records = records
        self.db = file

    def open(self):
        #
        # 以读写形式打开文件有两种方式，分别是 'r+', 'w+'
        #
        # 使用 'r+' 模式时，如果文件存在，则不会破坏原有数据，
        # 如果文件不存在，也不会创建。
        #
        # 使用'w+' 模式时，如果文件存在，将会删除原来所有的内容，
        # 如果文件不存在，就会创建。
        #
        try:
            db = open(self.dbpath, 'r+')
        except FileNotFoundError:
            db = open(self.dbpath, 'w+')
        return db

    def record_exists(self, name):
        """通过名字来判断记录是否已经存在于数据库中"""
        for record in self.records:
            if name == record['name']:
                return True
        return False

    def save(self, record, orig_name=None):
        """把info格式化（用Database.sep分隔，组合成一行），写入数据库"""
        # 找出可能存在的旧记录，删除，再把修改好的（新增的）附加在尾部，
        # 然后保存。
        self._delete(orig_name or record['name'])
        self.records.append(record)
        self._save()

    def _delete(self, name):
        for idx, record_in_db in enumerate(self.records):
            if record_in_db['name'] == name:
                del self.records[idx]
                break

    def _save(self):
        self.db.seek(0)
        self.db.truncate(0)
        for r in self.records:
            text = '%s:%s:%s\n' % (r['name'], r['gender'], r['phone'])
            self.db.write(text)
        self.db.flush()

    def delete(self, name):
        self._delete(name)
        self._save()

    def search(self, name):
        for record in self.records:
            if name in record['name']:
                return record


def get_name():
    try:
        name = sys.argv[2]
    except IndexError:
        name = None
    return name


if __name__ == '__main__':

    # 处理命令行参数
    if len(sys.argv) < 2:
        print('usage: %s cmd <arguments>' % basename(sys.argv[0]))
        exit(1)

    subcmd = sys.argv[1]

    if subcmd == 'add':
        contact = Contact()
        contact.fill()
        contact.save()
    elif subcmd == 'del':
        name = get_name()
        if not name:
            print('require a name')
            exit(1)
        contact = Contact.search(name)
        if contact:
            contact.delete()
        else:
            print('contact not found')
    elif subcmd == 'mod':
        name = get_name()
        if not name:
            print('require a name')
            exit(1)
        contact = Contact.search(name)
        contact.fill()
        contact.save()
    elif subcmd == 'list':
        Contact.list_all()
    elif subcmd == 'search':
        name = get_name()
        contact = Contact.search(name)
        if contact:
            print(contact)
        else:
            print('not found')
    else:
        print('command %s is not supported' % subcmd)
